New chat Search <#> Customize Chats Projects Artifacts Claude Code RecentsHide * Summer research interview for 6G mmWave transceiver testing * Translation and problem solving * DHT22 sensor with OLED and neopixel output documentation * ESP32 S3 vs C3 and camera module setup * Mixing RAM from different manufacturers * Stretchable and self-healing electronics overview * Interface and application programming concepts * C-clamp design fix for hair cutter mount * UBISS workshop registration funding request * UR10e hair clipper end effector design * Stretchable electronics research opportunity inquiry * Elektroniikan perusteet ennen kurssia * LCD touchscreen with Pac-Man animations and sensor readings * Circular wooden patio table design in Fusion 360 * Calculus exam preparation resources * Linear algebra vector spaces and basis explained * Fab academy final project ideas * OpenSCAD cylindrical box with lid * Input devices documentation with STEMMA QT sensor * Fab academy input devices documentation with accelerometer * HTML code insertion * Interactive Cybersecurity Folder Graphic * Fixing Website Navigation: Hiding Unwanted Links * Finding Diagonal Elements of Similar Matrices * Solving a Linear Pipesystem Problem J joooooooooooPro plan Interface and application programming concepts okay so I want to do interface and application programming week for fab academy. I want to know all the required concepts before we do anything so list the stuff i should know and then also explain it in detail. 20 Mar Architected comprehensive guide with organized sections and visuals Architected comprehensive guide with organized sections and visuals Interface and Application Programming week is one of the more open-ended Fab Academy weeks — you're building a custom UI that talks to hardware you've made. Here's everything you need to know, conceptually and practically. ------------------------------------------------------------------------ The core idea You're creating software that lets a human interact with a physical device. This means you need to understand three things: how to build the interface itself, how to communicate between the computer and the device, and how to handle data flowing between them. Let me map out the full landscape first: V Connecting to visualize... Click any node to dive deeper. Now let's go through each concept in detail. ------------------------------------------------------------------------ 1. Interface frameworks — what you build with You have three main options. Each has tradeoffs: *Web (HTML + JavaScript)* is the most accessible. Any browser is your runtime, you get sliders, charts, and buttons for free with libraries, and WebSerial API now lets you talk directly to USB devices without a backend. This is the most popular choice in recent Fab Academy cohorts. *Python (Tkinter, PyQt, or just terminal)* is great if you're already comfortable with Python from your embedded work. |pyserial| makes serial communication trivial. Tkinter is ugly but ships with Python; PyQt is polished but heavier. *Processing* is a Java-based creative coding environment designed for exactly this — visual interfaces connected to hardware. It has a native |Serial| library, and the syntax is intentionally simple. It was the standard Fab Academy tool for years. ------------------------------------------------------------------------ 2. Serial communication — the bridge This is the most critical concept. Your microcontroller and your computer talk through a serial port (USB-Serial bridge on most Fab boards). V Connecting to visualize... *The key insight*: serial is just a stream of bytes. You agree on a format (a protocol) on both sides. The simplest: have your MCU send |"sensor=42\n"| and your app splits on |=| and converts the second part to a number. That's it — that's a protocol. *Baud rate* is how fast bits are sent. Both sides must agree. 9600 is safe and slow; 115200 is common for modern Fab boards. Mismatch = garbage data. *Port names* vary by OS. On Linux/Mac it's |/dev/ttyUSB0| or |/dev/tty.usbserial-something|. On Windows it's |COM3| etc. Detecting it programmatically with |pyserial|'s |serial.tools.list_ports| is good practice. ------------------------------------------------------------------------ 3. Event-driven programming GUIs don't run top-to-bottom like a script. They wait. When something happens (button click, slider move, data arrives), they react. This is the event loop model. V Connecting to visualize... You don't call your handler — the framework does. You say: "when the button is clicked, call |this function|". That function is your *callback*. In JavaScript it's |button.addEventListener('click', myFn)|. In Tkinter it's |Button(command=my_fn)|. ------------------------------------------------------------------------ 4. Threading — the tricky part Here's where beginners get stuck. You have two things that need to run "simultaneously": * The *GUI event loop* — must never be blocked or it freezes * *Serial reading* — blocks, waiting for data from the device If you try to read serial in the same thread as your GUI, the whole UI hangs. The solution is a *background thread* that reads serial and puts data into a shared queue, which your GUI thread drains safely on a timer tick. This is the most common architecture for Python serial GUIs and it's worth understanding deeply before you write a line of code. ------------------------------------------------------------------------ 5. Data serialization and parsing When your MCU sends |"temp=23.5,hum=61\n"|, your computer receives raw bytes: |116 101 109 112 61...|. You need to: 1. Decode the bytes to a string (UTF-8) 2. Split on delimiters (|=| and |,|) 3. Convert strings to numbers (|float("23.5")|) 4. Map them to variables your UI uses This sounds trivial but edge cases bite: partial lines, garbled bytes, missing values, buffer overflow. Always validate before converting, and handle exceptions. ------------------------------------------------------------------------ 6. The visual output layer — charts and live plots For most Fab Academy projects you want to see sensor data moving in real time. Key libraries: *In Python*: |matplotlib| with animation callbacks, or |pyqtgraph| (much faster for live data), or |plotly|/|dash| for web-based output. *In JavaScript*: |Chart.js| is simple; |D3.js| is powerful and flexible; |Plotly.js| gives you live-updating scientific charts in minutes. *In Processing*: |draw()| runs in a loop — just clear the canvas and redraw each frame. Very natural for real-time visualisation. ------------------------------------------------------------------------ 7. WebSerial (browser-native serial) Modern Chrome/Edge support the WebSerial API, which lets a webpage directly access USB serial ports — no Python, no drivers. This is becoming the go-to approach because the web platform has fantastic UI tools (CSS, Canvas, D3) and deployment is just sharing a URL. The model: user clicks "Connect" → browser asks permission → you get a |port| object → read a |ReadableStream| → push data to your chart. ------------------------------------------------------------------------ Summary of what to know before you build Concept Why it matters Serial communication The pipe between your device and your app Protocol design You decide the message format on both ends Port detection and opening Finding and connecting to your device reliably Event-driven programming How GUIs work — callbacks, not loops Threading Keeping the UI alive while reading serial Data parsing Turning byte streams into usable numbers Live charting Showing data visually as it arrives Your chosen framework Web JS / Python / Processing — pick one A good starting path: pick *Python + pyserial* for your first pass (easiest serial handling), get data printing to the terminal, then add a Tkinter or |matplotlib| window around it. Once that works, you understand the whole chain and can move to a fancier web-based UI if you want. Want me to go deep on any specific concept, or start building something concrete — like a live sensor dashboard or a serial protocol? How does the microcontroller side of serial communication work? 20 Mar Orchestrated comprehensive microcontroller serial communication breakdown Orchestrated comprehensive microcontroller serial communication breakdown Great question to dig into. The microcontroller side is where most beginners have their first "why is nothing working" moment — understanding it properly saves a lot of frustration. What serial actually is at the hardware level Your MCU has a hardware peripheral called a *UART* (Universal Asynchronous Receiver-Transmitter). It converts parallel data (a whole byte at once inside the chip) into a single stream of bits over one wire, and vice versa. On Fab Academy boards (SAMD11, ATtiny, RP2040, etc.), a USB-Serial chip bridges this to the USB port your computer sees. The key word is *asynchronous* — there's no shared clock wire. Instead, both sides agree on a *baud rate* in advance, and timing is how they synchronize. If the baud rates don't match, you get garbage. V Connecting to visualize... This is the physical reality under every |Serial.println()| call. The hardware handles it automatically — you never deal with individual bits — but understanding this is why baud rate matters so much. ------------------------------------------------------------------------ The firmware API: what you actually write On Arduino-compatible boards, the serial API has three phases: *Setup* — call |Serial.begin(baudRate)| once in |setup()|. This configures the UART hardware. The number must exactly match what your computer's app uses. *Sending data* — |Serial.print()|, |Serial.println()|, and |Serial.write()|. The difference: |print| and |println| format values as human-readable text (|42| becomes the characters |'4'| and |'2'|). |write| sends raw bytes. For Fab Academy you almost always want |print|/|println| because text is much easier to parse on the computer side. *Receiving data* — this is where it gets interesting. The UART hardware fills a *receive buffer* in the background. Your code has to go check it. |Serial.available()| tells you how many bytes are waiting. |Serial.read()| pulls one byte out. Let me show you the full lifecycle of both sending and receiving: V Connecting to visualize... Step through all six stages — it walks you from |Serial.begin| all the way through parsing commands. ------------------------------------------------------------------------ The most important mental model: the receive buffer This trips up almost everyone. The UART hardware has a small buffer (typically 64 bytes on Arduino) that fills up in the background whether your code is checking or not. If your |loop()| is doing something slow and bytes pile up faster than you drain them, the buffer overflows and you lose data. The solution is to keep your |loop()| fast — never use |delay()| if you care about serial reliability. The send side also has a buffer, so |Serial.println()| returns almost instantly — it just queues the bytes, the hardware sends them asynchronously. This is why you can call it freely without worrying about timing. ------------------------------------------------------------------------ Common patterns in Fab Academy projects The most common architecture you'll see is *one-way streaming* — the MCU constantly blasts sensor readings at a fixed interval (say, every 100ms), and your computer app just reads and visualizes them. Simple and works great for dashboards. The second pattern is *command-response* — your computer sends a command, the MCU acts on it and sends back an acknowledgment or result. This is what you need for controlling actuators from a GUI button. The third is *both at once* — the MCU continuously streams sensor data, and also listens for commands. This is totally fine since UART is full- duplex (TX and RX are separate wires). The accumulate-into-buffer pattern on the receive side handles this cleanly without blocking the transmit side. ------------------------------------------------------------------------ The hardware path: from TX pin to your app Worth knowing exactly what happens physically: your MCU's TX pin → goes into a USB-Serial chip on your Fab board (like a CH340 or ATSAMD21's built-in USB) → appears as a virtual COM port on your OS → your Python/ JS app opens that port by name → reads the byte stream. The USB-Serial chip handles all the electrical conversion — your code never touches it. You just use the serial API and the hardware handles the rest. Want to go into any of these stages deeper — like the exact Python code on the computer side that reads and parses what your MCU sends? what is the whole mqtt and other stuff i need to know 20 Mar Synthesized MQTT protocols and communication ecosystems for technical education Synthesized MQTT protocols and communication ecosystems for technical education Good timing to cover this — MQTT sits in a whole family of communication approaches, and knowing where it fits saves you from picking the wrong tool for your project. The big picture: why there are so many protocols Serial (what we just covered) is a direct wire between one device and one computer. The moment you want your device to talk over WiFi, talk to multiple computers, or talk to the internet — you need a network protocol. There's a spectrum from simple to complex: V Connecting to visualize... Now let's go deep on each one. ------------------------------------------------------------------------ 1. HTTP — the foundation everything is built on HTTP is a *request/response* protocol. One side asks, the other answers, then the connection closes. You already use it every time you open a webpage. For Fab Academy, HTTP matters in two directions. First, your *MCU can host a tiny web server* — boards like the ESP32 or ESP8266 have WiFi and can run a web server that serves a control page when you visit its IP address in a browser. Second, your MCU can act as an *HTTP client* — making |GET| or |POST| requests to send data to a cloud service or receive commands. The fundamental problem with HTTP for hardware interfaces is that it's *pull-based* — your computer has to keep asking "anything new?" (called polling). If you want sensor data every 100ms, you have to make 10 requests per second. That's wasteful and creates latency. This is why the other protocols exist. Key HTTP concepts you need: |GET| — fetch something. |GET /sensor| returns the current reading. |POST| — send something. |POST /led| with body |{"state":"on"}| turns the LED on. *Status codes* — |200| means OK, |404| not found, |500| server error. *JSON* — the standard format for sending structured data in HTTP bodies. *REST* — a convention for how to name and structure your HTTP routes (things like |/sensors/temperature| rather than |/doStuff|). ------------------------------------------------------------------------ 2. WebSockets — the upgrade to persistent connections WebSockets solve HTTP's polling problem by keeping the connection open permanently after a handshake. Once connected, either side can send a message at any time — no request needed. V Connecting to visualize... WebSockets are perfect for a Fab Academy dashboard: your ESP32 runs a WebSocket server, your browser connects once, and sensor data streams in real time while you send commands back over the same connection. Libraries like |ESPAsyncWebServer| on the ESP32 side and the native |WebSocket| API in any browser make this very accessible. ------------------------------------------------------------------------ 3. MQTT — the one built for IoT MQTT (Message Queuing Telemetry Transport) was designed specifically for constrained devices on unreliable networks. It uses a *publish/subscribe model* with a central *broker* sitting in the middle. This is the most important protocol to understand conceptually for IoT work: V Connecting to visualize... Topics — the core concept of MQTT Everything in MQTT is organized by *topics*, which are just strings that look like file paths. A device publishes a message /to/ a topic. Subscribers tell the broker which topics they care about, and the broker delivers messages accordingly. |fab/room1/temperature → "23.5" fab/room1/humidity → "61" fab/room1/led/command → "ON" fab/room2/temperature → "21.0"| Subscribers can use wildcards: |fab/room1/#| gets everything under room1. |fab/+/temperature| gets temperature from any room. This makes MQTT incredibly flexible for multi-device systems. QoS levels MQTT has three Quality of Service levels that control delivery guarantees: |QoS 0| — fire and forget. Message might get lost. Fine for frequent sensor readings where losing one doesn't matter. |QoS 1| — delivered at least once. The broker retries until acknowledged. May arrive twice. |QoS 2| — exactly once. Guaranteed, but slowest. For commands where duplicates would cause problems (like "dispense medication once"). For most Fab Academy projects, QoS 0 is fine for sensor data, QoS 1 for commands. The retained message feature A retained message is stored by the broker. Any new subscriber immediately gets the last retained message for a topic — they don't have to wait for the next publish. This is extremely useful: your dashboard gets the current sensor value the moment it connects, rather than waiting up to 30 seconds for the next reading. What you need running A *broker* — the most common is *Mosquitto*, which runs locally. Install it on your laptop, and all your devices and apps connect to your laptop's IP address. For internet access, *HiveMQ* and *EMQX* offer free cloud brokers. For prototyping, |broker.hivemq.com| is a public test broker anyone can use. MQTT on the MCU side On an ESP32 with WiFi, you use the |PubSubClient| library (Arduino) or |umqtt| (MicroPython): cpp |#include #include WiFiClient wifi; PubSubClient mqtt(wifi); void setup() { WiFi.begin("ssid", "password"); mqtt.setServer("192.168.1.100", 1883); // your broker IP mqtt.setCallback(onMessage); // called when subscribed message arrives } void loop() { mqtt.loop(); // must call this — processes incoming messages // publish a reading every 5 seconds if (millis() - lastSend > 5000) { float temp = readSensor(); mqtt.publish("fab/sensor/temp", String(temp).c_str()); lastSend = millis(); } } void onMessage(char* topic, byte* payload, unsigned int len) { String msg = String((char*)payload).substring(0, len); if (msg == "ON") digitalWrite(LED, HIGH); }| MQTT on the Python side python |import paho.mqtt.client as mqtt def on_message(client, userdata, msg): value = float(msg.payload.decode()) print(f"{msg.topic}: {value}") client = mqtt.Client() client.on_message = on_message client.connect("localhost", 1883) client.subscribe("fab/#") # subscribe to everything under "fab/" client.loop_forever() # blocks, calling on_message each time a message arrives| ------------------------------------------------------------------------ 4. The network stack underneath everything All of these protocols run on top of the same underlying layers. Worth knowing the vocabulary: |IP| — every device on the network has an IP address. Your ESP32 gets one from your router via DHCP. |TCP| — reliable, ordered delivery. HTTP, WebSockets, and MQTT all run over TCP. |UDP| — fast, no guarantees. Used for things like mDNS (device discovery). |Port| — a number that distinguishes which service on a machine you're talking to. HTTP is port 80, MQTT is 1883, WebSockets on HTTPS is 443. When your Python app connects to MQTT at |192.168.1.100:1883|, it's connecting to IP |192.168.1.100| on port |1883| over TCP. ------------------------------------------------------------------------ 5. mDNS — finding your device without knowing its IP Instead of hardcoding |192.168.1.47| (which changes every time your device reboots), your ESP32 can advertise itself as |myfabdevice.local| on your local network. This is mDNS (Multicast DNS). On the ESP32 it's one line: |MDNS.begin("myfabdevice")|. Then your browser and Python app can connect to |myfabdevice.local| directly. Much more robust. ------------------------------------------------------------------------ How to choose for Fab Academy Situation Use Single device, laptop nearby, don't need WiFi Serial (simple wins) ESP32, want a browser control page HTTP server + WebSockets on the MCU Multiple devices, or device + dashboard + data logger all at once MQTT Sending data to a cloud service (IFTTT, Adafruit IO, etc.) HTTP POST or MQTT to cloud broker Need a web dashboard that updates live WebSockets or MQTT-over-WebSockets For most Fab Academy final projects that involve networking, *MQTT is the right answer* because it decouples your devices from your interface — you can add a data logger, a second dashboard, a mobile app, all without changing any device firmware. The broker handles the routing. Want to go deep on any specific piece — like setting up Mosquitto, or building a live web dashboard that subscribes to MQTT? so what do people normally do and what can i do if i wanna learn the most about these concepts. skip java stuff 20 Mar Synthesized Fab Academy practices and non-Java learning pathways Synthesized Fab Academy practices and non-Java learning pathways Great constraint — skipping Java/Processing leaves you with the tools that are actually used in industry. Here's the honest picture: What most Fab Academy students actually do The majority do one of two things. The *easy path* is an ESP32 serving a webpage with some sliders and a live sensor value — it works, it's fine, but you learn relatively little about programming because the ESP32 libraries do most of the heavy lifting and you copy-paste a HTML page into a string. The *better path* is a Python script that reads serial and draws a live chart, which teaches you more but still doesn't touch networking at all. Both pass the assignment. Neither teaches you much about interfaces or protocols at a deep level. What you should do if you want to actually learn Here's a project architecture that touches every concept we've covered, in a natural progression from simple to complex: *Build a full-stack sensor dashboard from scratch — no pre-built IoT platforms, no shortcuts.* The stack: V Connecting to visualize... Building this touches serial, threading, MQTT, WebSockets, HTTP, and frontend JS all in one project. Here's exactly what you learn at each layer and why it matters: ------------------------------------------------------------------------ Layer 1 — MCU firmware *What you build:* your Fab board reads a sensor and spits out clean serial. Nothing new here, but make it send structured data: c |// send as CSV with labels every 200ms Serial.print("t="); Serial.print(temperature); Serial.print(",h="); Serial.println(humidity);| *What you learn:* designing a protocol. The shape of this string has to be agreed on by the Python layer — you're making an API between two programs you wrote. ------------------------------------------------------------------------ Layer 2 — Python backend (the most educational layer) *What you build:* a Python script using *FastAPI* that does three things simultaneously — reads serial in a background thread, publishes readings to MQTT, and exposes a WebSocket that your browser connects to. python |import serial, threading, asyncio from fastapi import FastAPI, WebSocket import paho.mqtt.client as mqtt_client app = FastAPI() clients = [] # connected browser WebSocket sessions latest = {} # most recent sensor values def read_serial(): port = serial.Serial('/dev/ttyUSB0', 115200) while True: line = port.readline().decode().strip() # "t=23.5,h=61" # parse it for pair in line.split(','): k, v = pair.split('=') latest[k] = float(v) # publish to MQTT mqtt.publish("fab/sensor/temp", latest.get('t')) # push to all WebSocket clients asyncio.run(broadcast(latest)) threading.Thread(target=read_serial, daemon=True).start() @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): await ws.accept() clients.append(ws) try: while True: cmd = await ws.receive_text() # commands from browser handle_command(cmd) finally: clients.remove(ws) async def broadcast(data): for ws in clients: await ws.send_json(data)| *What you learn:* threading (serial runs separately from the web server), async programming (WebSocket handling), the difference between sync and async code, how a server manages multiple simultaneous connections, and why you need a queue/broadcast pattern. FastAPI is excellent to learn because it's what Python developers actually use in industry, its automatic docs page (|/docs|) shows you your own API in a browser, and it handles the WebSocket protocol for you so you understand the concept without reimplementing it. ------------------------------------------------------------------------ Layer 3 — Mosquitto *What you build:* install Mosquitto, run it locally, subscribe to your topics in a terminal window while your Python script publishes. bash |# terminal 1 — run the broker mosquitto # terminal 2 — watch messages arriving in real time mosquitto_sub -h localhost -t "fab/#" -v # terminal 3 — send a test command mosquitto_pub -h localhost -t "fab/led/command" -m "ON"| *What you learn:* the pub/sub mental model becomes concrete when you can see messages flowing in a terminal. You also learn that MQTT and WebSockets are independent — your browser talks WebSocket to your Python server, while your Python server talks MQTT to the broker. These are two different protocols serving two different purposes. This is also where you can add a second "subscriber" — maybe a small Python script that just logs everything to a CSV file — and realize neither the MCU nor the browser had to change at all. That's the power of the broker model. ------------------------------------------------------------------------ Layer 4 — Frontend (vanilla JS, no framework) *What you build:* a plain HTML file — no React, no Vue, no build tools. Just a || for Chart.js and a WebSocket connection. javascript |const ws = new WebSocket('ws://localhost:8000/ws'); const chart = new Chart(ctx, { type: 'line', data: { datasets: [{ label: 'temp', data: [] }] } }); ws.onmessage = (event) => { const data = JSON.parse(event.data); chart.data.datasets[0].data.push({ x: Date.now(), y: data.t }); if (chart.data.datasets[0].data.length > 100) chart.data.datasets[0].data.shift(); chart.update('quiet'); }; document.getElementById('led-on').onclick = () => ws.send('LED:ON'); document.getElementById('led-off').onclick = () => ws.send('LED:OFF');| *What you learn:* the browser's native WebSocket API, how the event loop works in a browser (same concept as the MCU's loop), JSON parsing, and live chart updates. Doing this in vanilla JS before touching any framework means you understand what frameworks are actually abstracting. ------------------------------------------------------------------------ The learning order matters Do it in this exact sequence — don't skip ahead: 1. Get serial working and printing to terminal. Confirm the data looks right. 2. Add the Python threading layer. Print parsed values to console. 3. Add MQTT publishing. Watch in |mosquitto_sub| terminal. 4. Add FastAPI + WebSocket. Test with the browser's built-in WebSocket debugger (devtools → network → WS). 5. Build the frontend. Connect to WebSocket, log raw messages to |console.log| first. 6. Add the chart. Add buttons for commands. Each step has a working, testable state before you add complexity. This is how engineers actually build things. ------------------------------------------------------------------------ What to skip Don't use *Node-RED* — it hides everything behind drag-and-drop and you learn nothing. Don't use *Adafruit IO* or *ThingSpeak* — same problem, the interesting parts are invisible. Don't use a frontend framework (React, Vue) yet — you don't need it for one page and the abstraction will confuse you when you're also learning WebSockets for the first time. ------------------------------------------------------------------------ Honest time estimate Getting to a working serial → Python → WebSocket → browser chart (skipping MQTT for now) is about a focused afternoon if you're comfortable with Python basics. Adding MQTT is another hour or two. The frontend with a nice live chart is another afternoon. The whole thing — maybe 2 solid days. But you'll come out understanding a production IoT stack, not just a Fab Academy assignment. Want me to start building any specific layer with you? I'd suggest starting with the Python serial reader and FastAPI WebSocket server — that's the hardest and most educational piece. okay lets do this. What sensor should i choose like an imu, temperature and humidiity or what 20 Mar Weighed sensor options against project goals and requirements Weighed sensor options against project goals and requirements Good question to nail down before writing a line of code — the sensor shapes what your dashboard can actually do. Let me ask a few things to give you a real recommendation: Q: What do you want the dashboard to feel like? A: Not sure yet Q: Do you have a final project in mind that this could connect to? A: No, this is standalone for the week Q: What hardware do you have or can easily get? (Select all that apply) A: ESP32 / ESP8266 (WiFi board) 20 Mar Weighed sensor options for maximum learning and visual engagement Weighed sensor options for maximum learning and visual engagement Perfect setup — ESP32 with WiFi means you can use the full stack (WiFi instead of USB serial, MQTT over the network, proper WebSocket). Standalone is fine, gives you full freedom on the sensor choice. The recommendation: MPU6050 IMU Skip temperature/humidity. Here's why it's the wrong choice for this project — it changes slowly (once every few seconds at best), which makes your "live" dashboard feel dead. Threading and WebSocket streaming are wasted on data that barely moves. The *MPU6050* is the right sensor for this week because it gives you 6 channels of fast-moving data simultaneously — accelerometer X/Y/Z and gyroscope X/Y/Z — all streaming at up to 100Hz. That makes every concept you're learning actually feel alive: V Connecting to visualize... What your finished project will look like With an MPU6050 + ESP32 + the stack we planned, your dashboard will have live scrolling line charts for all 6 axes, a 3D rotating cube that mirrors the physical orientation of the sensor in real time, tilt/shake detection shown as alerts, and a control panel that sends commands back (like changing the sample rate or toggling an LED on the ESP32). That's something genuinely impressive to document for Fab Academy — and every piece of it involves a real concept. The wiring MPU6050 connects to ESP32 over I2C — just 4 wires: |MPU6050 VCC → ESP32 3.3V MPU6050 GND → ESP32 GND MPU6050 SCL → ESP32 GPIO 22 MPU6050 SDA → ESP32 GPIO 21| That's it. The |Adafruit MPU6050| Arduino library handles all the register reading. You get clean float values in 3 lines of code. One decision to make before we start Since you have an ESP32 with WiFi, you have two options for getting data off the board: *Option A — USB Serial → Python* (what we planned): ESP32 sends data over USB cable to your laptop, Python reads it with pyserial, then publishes to MQTT and WebSocket. Teaches the full stack we designed. Needs the laptop nearby. *Option B — ESP32 publishes to MQTT directly over WiFi*: ESP32 connects to your WiFi, publishes directly to Mosquitto on your laptop. No USB needed after flashing. Slightly simpler Python backend (no serial thread), but you skip the serial/threading layer. Option A teaches more (serial + threading + MQTT + WebSocket). Option B is more "IoT-realistic" (the device is truly wireless). You could actually do both — start with A to get data flowing fast, then cut the cable and switch to B once everything works. Which feels right to you — or should we just start with A and switch to B later? lets go A 20 Mar Architected layered IoT system starting with firmware development Architected layered IoT system starting with firmware development Let's build it layer by layer. We'll get each layer working and tested before touching the next one. Starting with the firmware. ------------------------------------------------------------------------ Layer 1 — ESP32 firmware First, install the MPU6050 library. In Arduino IDE: Sketch → Include Library → Manage Libraries → search *"Adafruit MPU6050"* → install it and all its dependencies when prompted. Then flash this: cpp |#include #include #include Adafruit_MPU6050 mpu; void setup() { Serial.begin(115200); while (!Serial) delay(10); if (!mpu.begin()) { Serial.println("ERROR:MPU6050 not found"); while (1) delay(10); } mpu.setAccelerometerRange(MPU6050_RANGE_8_G); mpu.setGyroRange(MPU6050_RANGE_500_DEG); mpu.setFilterBandwidth(MPU6050_BAND_21_HZ); Serial.println("READY"); } void loop() { sensors_event_t accel, gyro, temp; mpu.getEvent(&accel, &gyro, &temp); // CSV: ax,ay,az,gx,gy,gz Serial.print(accel.acceleration.x, 2); Serial.print(","); Serial.print(accel.acceleration.y, 2); Serial.print(","); Serial.print(accel.acceleration.z, 2); Serial.print(","); Serial.print(gyro.gyro.x, 2); Serial.print(","); Serial.print(gyro.gyro.y, 2); Serial.print(","); Serial.println(gyro.gyro.z, 2); delay(20); // 50Hz — fast enough, not overwhelming } ``` Open the Serial Monitor at **115200 baud**. You should see something like: ``` READY 0.12,-0.05,9.78,0.01,-0.00,0.02 0.11,-0.04,9.79,0.01,-0.01,0.02| Tilt the board — the numbers should change. If you see |ERROR:MPU6050 not found|, check your wiring (SDA/SCL are the usual culprits — swap them and try again). *Test before continuing.* Don't move to Python until this is printing clean lines. ------------------------------------------------------------------------ Layer 2 — Python backend You need three things installed. Run this: bash |pip install pyserial fastapi uvicorn paho-mqtt websockets| Now create a project folder and make this file — |backend.py|: python |import serial import serial.tools.list_ports import threading import asyncio import json import time from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import HTMLResponse import paho.mqtt.client as mqtt # ── config ────────────────────────────────────────────── BAUD = 115200 MQTT_HOST = "localhost" MQTT_PORT = 1883 TOPIC = "fab/imu" # ──────────────────────────────────────────────────────── app = FastAPI() # shared state latest = {} clients = [] # connected WebSocket sessions lock = threading.Lock() # ── auto-detect serial port ────────────────────────────── def find_port(): ports = serial.tools.list_ports.comports() for p in ports: if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340", "SILABS"]): return p.device return None # ── serial reader thread ───────────────────────────────── def serial_reader(): port_name = find_port() if not port_name: print("ERROR: no serial port found") return print(f"connecting to {port_name}") ser = serial.Serial(port_name, BAUD, timeout=1) time.sleep(2) # wait for ESP32 to boot while True: try: line = ser.readline().decode("utf-8").strip() if not line or line == "READY": continue parts = line.split(",") if len(parts) != 6: continue data = { "ax": float(parts[0]), "ay": float(parts[1]), "az": float(parts[2]), "gx": float(parts[3]), "gy": float(parts[4]), "gz": float(parts[5]), "ts": time.time() } with lock: latest.update(data) # publish to MQTT mqtt_client.publish(TOPIC, json.dumps(data)) # broadcast to WebSocket clients asyncio.run(broadcast(json.dumps(data))) except Exception as e: print(f"serial error: {e}") time.sleep(0.1) # ── MQTT setup ─────────────────────────────────────────── mqtt_client = mqtt.Client() try: mqtt_client.connect(MQTT_HOST, MQTT_PORT) mqtt_client.loop_start() print("MQTT connected") except Exception: print("MQTT broker not found — continuing without it") # ── WebSocket broadcast ────────────────────────────────── async def broadcast(message: str): dead = [] for ws in clients: try: await ws.send_text(message) except Exception: dead.append(ws) for ws in dead: clients.remove(ws) # ── FastAPI routes ─────────────────────────────────────── @app.get("/status") def status(): with lock: return {"connected": bool(latest), "latest": latest} @app.websocket("/ws") async def websocket_endpoint(ws: WebSocket): await ws.accept() clients.append(ws) print(f"browser connected — {len(clients)} client(s)") try: while True: msg = await ws.receive_text() print(f"command from browser: {msg}") # forward commands back to serial here if needed later except WebSocketDisconnect: clients.remove(ws) print("browser disconnected") # ── start serial thread on startup ─────────────────────── @app.on_event("startup") def startup(): t = threading.Thread(target=serial_reader, daemon=True) t.start()| Run it: bash |uvicorn backend:app --reload --port 8000 ``` You should see something like: ``` connecting to /dev/ttyUSB0 MQTT connected INFO: Application startup complete.| *Test it now* — before building the frontend. Open your browser and go to |http://localhost:8000/status|. You should see live JSON: json |{ "connected": true, "latest": { "ax": 0.12, "ay": -0.05, "az": 9.78, ... } }| Refresh it a few times — the values should change as you move the sensor. If |connected| is false, the serial thread hasn't parsed a line yet — check the terminal for error messages. ------------------------------------------------------------------------ Layer 3 — Mosquitto (while Python is running) Install Mosquitto if you haven't: bash |# macOS brew install mosquitto # Ubuntu/Debian sudo apt install mosquitto mosquitto-clients # Windows — download installer from mosquitto.org| Open a second terminal and run: bash |mosquitto_sub -h localhost -t "fab/imu" -v ``` You should see a flood of JSON messages rolling in: ``` fab/imu {"ax": 0.12, "ay": -0.05, "az": 9.78, "gx": 0.01, ...} fab/imu {"ax": 0.34, "ay": -0.08, "az": 9.75, "gx": 0.02, ...}| This is the moment the broker layer becomes real — you can open as many |mosquitto_sub| terminals as you want, subscribing to different topics, and all of them get the data independently without your firmware or Python backend knowing or caring. ------------------------------------------------------------------------ Layer 4 — Frontend Once |/status| is returning data and MQTT is flowing, create |dashboard.html| in the same folder: html | IMU Dashboard

IMU live dashboard — MPU6050

accel X
accel Y
accel Z
gyro X
gyro Y
gyro Z

Accelerometer (m/s²)

Gyroscope (rad/s)

| Open this file directly in your browser (|File → Open| or just double-click it). You should see two live scrolling charts and six updating numbers. The green dot in the top left confirms the WebSocket is connected. ------------------------------------------------------------------------ The test at each stage Here's exactly what working looks like at every checkpoint: V Connecting to visualize... Start with the firmware, get clean CSV in the serial monitor, then come back and we'll move to the Python layer together. What do you see when you flash it? Week 9: Input Devices // what I built

This week's group assignment was to explore and compare various software tools for building user interfaces that interact with microcontrollers. On the individual side, the goal was to build a working interface that talks to a physical input or output device — and actually understand every layer of how the data gets there.

Rather than taking the easy route of an ESP32 serving a pre-built webpage, I built the full stack from scratch. An ESP32 reads a LSM6DS33 IMU sensor over I2C and streams 6 channels of live motion data — accelerometer and gyroscope on X, Y and Z — over USB serial at 50Hz. A Python backend running FastAPI picks that up, parses it, and does two things simultaneously: publishes each reading to a local MQTT broker (Mosquitto) and pushes it over WebSocket to any connected browser. The browser dashboard is a plain HTML + JavaScript page with live scrolling charts for all 6 axes and buttons that send commands back down through the same stack to the ESP32.

Building it this way means every protocol in the chain is something I wrote and debugged myself — the serial format coming off the chip, the threading model keeping the Python server alive while reading serial, the MQTT topic structure, the WebSocket broadcast logic, and the JavaScript event handling in the browser. Each layer has a clear test before the next one gets built.

LayerTechnologyWhat it does
Sensor → MCUI2CLSM6DS33 sends readings to ESP32 over I2C at address 0x6A
MCU → laptopSerialESP32 sends CSV lines over USB at 115200 baud, 50Hz
Laptop → brokerMQTTPython publishes each reading to topic fab/imu on a local Mosquitto broker
Laptop → browserWebSocketFastAPI pushes JSON to the browser as data arrives — no polling
BrowserJSVanilla JS + Chart.js renders live charts from WebSocket messages
|

Uses the Adafruit LSM6DS Arduino library. Reads sensor events every 20ms and prints them as a comma-separated line — the newline is the delimiter the Python backend waits for.

add 1 to 2 lines more detail on how the libarary in arduino can be downloaded from library managerr and bit more explaintion of how the code works 20 Mar 2 / 2 html |

Uses the Adafruit LSM6DS Arduino library. Install it via Arduino IDE → Sketch → Include Library → Manage Libraries → search "Adafruit LSM6DS" and install along with its dependency "Adafruit Unified Sensor" when prompted. The library abstracts all the I2C register reads — you just call getEvent() and get clean float values back.

In setup() the sensor is initialised over I2C and configured — range sets the maximum measurable value (±8G for accel, ±500°/s for gyro) and data rate sets how fast the sensor samples internally (52Hz here). In loop(), getEvent() fills three event structs with the latest accel, gyro and temperature readings. The six motion values are then printed as a comma-separated line with println() — the newline character at the end is what the Python backend uses as a message delimiter to know a complete packet has arrived.

| okay so far intro is done and then we have moved onto wiring and since i have already used this exact setup in week 9 which was my input devices do i have to add anymore detail or anything before i move on 20 Mar Since you already documented the LSM6DS33 wiring in Week 9, you don't need to repeat it here. Just add a one-liner that points back to that page and keep your wiring table as a quick reference. Something like this to replace your current wiring intro paragraph: html |

The LSM6DS33 wiring is identical to what I used in Week 9: Input Devices — I2C over four wires to the ESP32. Full wiring detail, photos and a logic analyzer trace are documented there. Quick reference below:

| Then keep the wiring table as-is since it's just four rows — useful to have on the page so the reader doesn't have to jump away for something basic. You don't need a photo here either since Week 9 already has one. Move straight on to firmware.

Intro

This week's group assignment was to explore and compare various software tools for building user interfaces that interact with microcontrollers. On the individual side, the goal was to build a working interface that talks t pasted so do you think till the hardware this is enough or should i add any more details. Plus should i be using visual studio code for python or thonny 20 Mar The content up to hardware is solid — intro explains the project well, wiring table is clean, and the firmware section has good explanation. Only thing missing is a serial monitor screenshot showing the CSV lines actually printing, which is worth adding when you get to that step since it's easy evidence that the firmware works. *On the editor question — use VS Code, not Thonny.* Thonny is designed for MicroPython on devices like the Pi Pico where you're running Python directly on the chip. Here your Python is running on your laptop, not the board — so Thonny gives you nothing useful. VS Code is the right tool because you can run |uvicorn| in the integrated terminal, edit |backend.py| and |dashboard.html| side by side, and it has good Python extension support. Install the Python extension by Microsoft when you open it. *One small thing I'd fix in the current doc* — your hardware section still has the generic intro paragraph from the original file: html |

The LSM6DS33 is a 6-axis IMU (accelerometer + gyroscope) from ST Microelectronics. It runs on 3.3V and communicates over I2C. Four wires to connect it to the ESP32:

| Since you're already linking back to Week 9, replace that with the shorter version we discussed: html |

The LSM6DS33 wiring is identical to what I used in Week 9: Input Devices — I2C over four wires to the ESP32. Full wiring detail and photos are documented there. Quick reference below:

| Otherwise you're good to move on to the Python backend section. When you're ready to document that, come back and let me know what actually happened when you ran it — any errors, what the |/status| endpoint returned, what the MQTT terminal showed — and I'll write the snippet around your actual experience rather than the plan. how should i set up the python environment in vs code to have a seperate virtual thingy or is that not needed 20 Mar For this project you don't strictly need one, but you should make one anyway — it's a 2-minute habit that saves a lot of pain and it's the correct way to work in Python. *Why it matters:* without a virtual environment, every |pip install| goes into your global Python installation. Do a few projects and packages start conflicting — one project needs |paho-mqtt 1.6|, another needs |2.0|, and they can't both live in global at the same time. A virtual environment is just an isolated folder that contains its own Python and packages, separate from everything else. *Set it up in VS Code:* Open your project folder in VS Code, then open the integrated terminal (|Ctrl+`` | or Terminal → New Terminal) and run: bash |# create the virtual environment — only do this once python -m venv venv| Then activate it: bash |# mac / linux source venv/bin/activate # windows venv\Scripts\activate| You'll see |(venv)| appear at the start of your terminal prompt — that means it's active. Now install your packages: bash |pip install pyserial fastapi uvicorn paho-mqtt websockets| These only install inside the |venv| folder, not globally. *The VS Code step people miss:* press |Ctrl+Shift+P| → type "Python: Select Interpreter" → choose the one that says |venv| in the path. This makes sure VS Code's linting and autocomplete uses the same packages you installed. *Every time you come back to the project*, just activate the venv again before running anything. VS Code actually does this automatically in the integrated terminal if you've selected the right interpreter. One more thing — add a |requirements.txt| so you have a record of what the project needs: bash |pip freeze > requirements.txt| That's it. Small effort, clean setup, and worth documenting in your Fab Academy page since it shows you understand proper Python project structure. what if i had a venv already does it clash with the previous one due to naming 20 Mar No, they don't clash — each venv is just a folder sitting inside whichever project directory you created it in. The name |venv| is just a convention, the folder itself is local to that project. So if you have: |week9/ venv/ ← that project's isolated environment backend.py week10/ venv/ ← completely separate, no relation to the above backend.py| Both folders are named |venv| but they have nothing to do with each other. Packages installed in one don't affect the other at all. The only way they'd clash is if you created both venvs in the *same folder*, which you wouldn't do. As long as each project has its own folder, just name them all |venv| — it's the standard name, |.gitignore| templates already exclude it by that name, and everyone reading your code will know what it is. One thing to check — if you open VS Code and the terminal already shows |(venv)| when you haven't activated anything, it means VS Code remembered the interpreter from a previous project. Just run the select interpreter step again (|Ctrl+Shift+P| → Python: Select Interpreter) and point it at the correct |venv| for the current project. PS E:\shahmeer\fab_academy> python -m venv venv PS E:\shahmeer\fab_academy> venv/Scripts/activate venv/Scripts/activate : File E:\shahmeer\fab_academy\venv\Scripts\Activate.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170. At line:1 char:1 + venv/Scripts/activate + ~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : SecurityError: (:) [], PSSecurityException + FullyQualifiedErrorId : UnauthorizedAccess PS E:\shahmeer\fab_academy> 20 Mar Classic Windows issue — PowerShell blocks scripts by default. Easy fix, run this: powershell |Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser| It will ask you to confirm, press |Y|. This only changes it for your user, not the whole system, so it's safe. Then try activating again: powershell |venv/Scripts/activate| You should see |(venv)| appear in the prompt. If VS Code's terminal is still being awkward, the other option is to just switch the terminal from PowerShell to *Command Prompt* — click the dropdown arrow next to the |+| in the terminal panel → select Command Prompt — and use this instead: cmd |venv\Scripts\activate.bat| Command Prompt doesn't have the script execution restriction at all. Either approach works, the RemoteSigned fix is the cleaner one though since you only have to do it once.

Python backend

I am using visual studio code for this. So first I set up the virtual environment. As I was setting it up, it gave me an error saying running scripts was disabled. So I went to powershell and ran the commands shown in the picture afte pasted lets do this part by part so I can kinda explain and also understand myself whats happening here 20 Mar Good approach. Let's go function by function. Starting with the very top — the imports and shared state: python |import serial, serial.tools.list_ports, threading, asyncio, json, time from fastapi import FastAPI, WebSocket, WebSocketDisconnect import paho.mqtt.client as mqtt app = FastAPI() clients, latest, lock = [], {}, threading.Lock()| *What's happening here:* |serial| and |serial.tools.list_ports| — the pyserial library, used to open the COM port and auto-detect which port the ESP32 is on. |threading| — lets us run the serial reader in the background so it doesn't block the web server. |asyncio| — Python's async system, needed because FastAPI and WebSockets are async — they can handle multiple connections at the same time without waiting for each other. |json| — converts the Python dict of sensor values into a JSON string to send over the network. |FastAPI, WebSocket, WebSocketDisconnect| — the web framework. FastAPI handles both normal HTTP routes (|/status|) and WebSocket connections (|/ws|). |paho.mqtt.client| — the MQTT library for publishing messages to the Mosquitto broker. Then the three shared variables: * |clients| — a list of every browser tab currently connected via WebSocket. When data arrives, we loop through this and send to all of them. * |latest| — a dict holding the most recent sensor reading. Used by the |/status| endpoint. * |lock| — a threading lock. Since the serial thread and the web server both touch |latest|, the lock prevents them from reading and writing at the exact same moment, which would corrupt the data. ------------------------------------------------------------------------ Does that make sense before we move to |find_port()| and the serial reader? Once you're happy with this bit write a sentence or two in your own words about what this section does and paste it — I'll help you turn it into a doc paragraph. lets add description of everything we did here before adding the code. dont make it too long 20 Mar html |

The backend is a single backend.py file that acts as the bridge between the ESP32 and the browser. It runs three things at once — a serial reader, an MQTT publisher, and a WebSocket server. At the top of the file we import all the libraries needed and set up three shared variables: clients (a list of connected browser tabs), latest (the most recent sensor reading), and lock (a threading lock that stops the serial thread and web server from reading and writing to latest at the same moment).

find_port() scans all available COM ports and returns the one that looks like a USB-Serial device — so we don't have to hardcode COM3 or whatever Windows assigns it. serial_reader() runs in a background thread, continuously reading lines from the ESP32, splitting the CSV into 6 floats, then publishing to MQTT and broadcasting to all WebSocket clients. The /status route is a quick HTTP endpoint to verify the backend is receiving data before touching the browser dashboard. The /ws route handles WebSocket connections — each new browser tab gets added to clients and removed when it disconnects.

| before doing this whole complex thiing should we just test if it can read the esp32 in python or something simple 20 Mar Yes, absolutely — don't run the full backend until you know Python can even see the ESP32. Run this first: python |import serial.tools.list_ports ports = serial.tools.list_ports.comports() for p in ports: print(p.device, p.description)| Save it as |test_ports.py| and run it in the VS Code terminal: bash |python test_ports.py ``` You should see something like: ``` COM6 USB-SERIAL CH340 (COM6)| If the ESP32 shows up there, do the next test — actually read from it: python |import serial ser = serial.Serial('COM6', 115200, timeout=2) # replace COM6 with yours while True: line = ser.readline().decode('utf-8').strip() if line: print(line)| Run it while the ESP32 is flashed and connected — you should see the raw CSV lines printing in the terminal exactly like the serial monitor in Arduino IDE. If you see that, Python can talk to the board and the hard part is already proven. Then we move to the full backend with confidence. What does |test_ports.py| show? okay serial port detected, and communcating what would be the next basic step 21 Mar Next step is just making sure Python can correctly *parse* one of those CSV lines into the 6 separate float values before we add any web server complexity. Run this: python |import serial ser = serial.Serial('COM6', 115200, timeout=2) # your port while True: line = ser.readline().decode('utf-8').strip() if not line or line == 'READY': continue parts = line.split(',') if len(parts) != 6: continue ax, ay, az, gx, gy, gz = [float(x) for x in parts] print(f"accel: x={ax:.2f} y={ay:.2f} z={az:.2f} | gyro: x={gx:.2f} y={gy:.2f} z={gz:.2f}") ``` You should see clean labelled output like: ``` accel: x=0.12 y=-0.05 z=9.78 | gyro: x=0.01 y=-0.00 z=0.02 accel: x=0.34 y=-0.03 z=9.76 | gyro: x=0.01 y=-0.01 z=0.01| Tilt the board — the accel values should visibly change. If that's working you've confirmed the full serial pipeline: ESP32 sending → Python receiving → parsing into usable numbers. After that the backend is just wrapping this same logic in FastAPI and pushing the data onwards. What do you see? shouldnt I run the arduino script before this on the esp32 so there is a program reading the data?? 21 Mar Yes exactly, good catch. The ESP32 needs to be flashed and running the firmware first otherwise there's nothing sending data on the serial port — Python would just get silence or garbage. So the order is: 1. Open Arduino IDE 2. Flash the LSM6DS33 firmware we wrote earlier 3. Check the serial monitor in Arduino IDE first — confirm you see the CSV lines printing 4. *Close the serial monitor* — this is important, only one program can use the serial port at a time, if Arduino's serial monitor is still open Python can't connect 5. Then run the Python test script The "close the serial monitor" step catches everyone at least once. If Python throws a |serial.SerialException: could not open port| error that's almost always the reason. what will sda and scl connect on xiao esp32c3 21 Mar The XIAO ESP32-C3 doesn't use GPIO 21/22 for I2C like a standard ESP32 — it has its own pinout. The default I2C pins are: * *SDA* → D4 (GPIO 6) * *SCL* → D5 (GPIO 7) So your wiring table in the doc should be updated to: LSM6DS33 XIAO ESP32-C3 VCC 3.3V GND GND SDA D4 SCL D5 You don't need to change anything in the firmware code itself — the Adafruit library calls |begin_I2C()| which automatically uses whatever the board's default I2C pins are. So on the XIAO ESP32-C3 it will automatically use D4/D5 without you specifying them explicitly. 1774068442364_image.png In week 9 i wrote this but i havent fully specified which example it is which i should and now I cant figure out which one it is 21 Mar Looking at your serial monitor output — the format shows Temperature, Accel X/Y/Z, and Gyro X/Y/Z on separate lines with labels like |Accel X: -3.91 Y: 0.20 Z: 9.34 m/s^2|. That's the *LSM6DS_Serial* example, which is the basic serial print example in the Adafruit LSM6DS library. In Arduino IDE it's at: |File → Examples → Adafruit LSM6DS → LSM6DS_Serial| That's the one to reference in your Week 9 doc. Worth noting though — that example's output format (labelled, multi-line) is different from what we need for this week. Our firmware outputs plain CSV like |0.12,-0.05,9.78,0.01,-0.00,0.02| which Python can split easily. The example code's human-readable format would be a pain to parse programmatically, which is exactly why we wrote our own firmware instead of using the example directly. 1774068533824_image.png the serial one doesnt seem to be there 21 Mar Looking at your examples list, the one that matches your Week 9 output (Temperature + Accel + Gyro with labels) is *|adafruit_lsm6ds33_test|* — that's the test example specifically for the LSM6DS33 chip which prints all three readings in that labelled format. So in your Week 9 doc you can specify: |File → Examples → Adafruit LSM6DS → adafruit_lsm6ds33_test| And for this week (Week 10) we're not using any example — our firmware is custom written specifically to output plain CSV that Python can parse. https://fab-academy-1d1046.gitlab.io/week%209.html check week 9 and see if everything is alright 21 Mar Fetched: ⚡ Week 9: Input Devices Fetched: ⚡ Week 9: Input Devices Week 9 looks really solid honestly — good structure, clear explanations, the I2C deep dive is well done, logic analyzer section is thorough. A few small things worth fixing: *Actual issues:* * Section 05 says "the values lookled like" — typo, fix to "looked" * The download link points to |sensor.imo/sensor.imo.ino| which looks wrong — |.imo| should probably be |.ino| * The video embed has a broken outer link wrapping it — there's an || tag around the || tag that wasn't closed properly before the video div. Can you show me the lines just above line 77? Probably around lines 70-77 — there's likely an unclosed || sitting there that's wrapping everything including the video.

05 — Programming the Sensor

First I downloaded the appropriate library for the LSM6DS33 sensor from library manager which was the Adafruit LSM6DS library. Then I used the example code provided by the library to read the accelerometer and gyroscope data from the sensor which can be found from File → Examples → Adafruit LSM6DS → adafruit_lsm6ds33_test. I changed the delay from 100ms to 2000ms so its easier to see the data change on the serial monitor. This is what the values lookled like as I moved the sensor:

IMU Readings

As you can see, the values change as I move the sensor.


Now since I wanted to read the angles of the board, I had to convert the accelerometer readings into angles. The accelerometer gives us the acceleration values along the X, Y, and Z axes.

The tilt angle is calculated using the arctangent function. This function determines the angle between components of the acceleration vector. In the code, the atan2() function is used because it handles both positive and negative values correctly and returns the angle in the correct quadrant.

For example, the pitch angle can be calculated from the accelerometer values using the following relationship:

θ = arctan( x / √(y² + z²) )

In this equation, x, y, and z represent the acceleration values measured along each axis. The result of the arctangent calculation is returned in radians. To convert the value into degrees, the result is multiplied by 180 / π.

As the board is tilted, the acceleration values change and the calculated angle updates accordingly. This allows the orientation of the sensor to be observed in real time through the serial monitor.

The following is the code used for calculating the tilt angles:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_LSM6DS33.h>
Adafruit_LSM6DS33 lsm6ds;
void setup() {
  Serial.begin(115200);
  while (!Serial);
  if (!lsm6ds.begin_I2C()) {
    Serial.println("LSM6DS33 not found");
    while (1);
  }
  Serial.println("LSM6DS33 ready");
}
void loop() {
  sensors_event_t accel;
  sensors_event_t gyro;
  sensors_event_t temp;
  lsm6ds.getEvent(&accel, &gyro, &temp);
  float ax = accel.acceleration.x;
  float ay = accel.acceleration.y;
  float az = accel.acceleration.z;
  // Calculate roll and pitch
  float roll  = atan2(ay, az) * 180.0 / PI;
  float pitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI;
  Serial.print("Roll: ");
  Serial.print(roll);
  Serial.print("   Pitch: ");
  Serial.println(pitch);
  delay(1500);
}
This is what it looked like as the board was moved around:
Show more 21 Mar Found it — line 83 "This is what it looked like as the board was moved around:" is sitting outside any tag, just loose text. That's what's creating the broken link appearance. Wrap it in a |

| tag: html |

This is what it looked like as the board was moved around:

| Also fix the two typos while you're in this section: * |lookled| → |looked| in the paragraph above the screenshot * |

| → |

| (lowercase) on the line before the code block — not a bug but inconsistent okay so that is now fixed so week 9 should be good now 21 Mar Yes week 9 is good. To summarise what's solid there: * I2C explanation and logic analyzer capture are thorough — that's the main evidence for the group/individual signal measurement requirement * The angle calculation code with the |atan2()| explanation is a nice extra * Wiring, scanner, and library steps are all documented * Video works now, typos fixed One thing still worth doing when you get a minute — add that one sentence about the I2C scanner result explicitly stating the address it found (|0x6A|). Right now you have the screenshot but no text calling it out. Something as simple as: |The scanner found the sensor at address 0x6A, confirming the wiring was correct.| That's it though. Week 9 is done, move on to Week 10. Where are you at with the firmware — did you get the CSV lines printing in the serial monitor? that is done so lets move back to interface stuff 21 Mar Good. So the current state of Week 10 is: * Intro ✅ * Wiring ✅ * Firmware ✅ Next is the Python backend. You already have the code written and we know the serial communication works from the test scripts you ran earlier. Before writing the backend section for the doc, tell me what actually happened when you ran it — did |uvicorn| start cleanly, did |/status| return live data, any errors you hit along the way? The doc should reflect what you actually experienced rather than just showing the code. 1774072644905_image.png why does it sometimes lag and not show anything in outpuit 21 Mar Looking at your terminal — the script connected fine (|Connected to COM9 at 115200 baud|) but then nothing printed after. A few things that cause this: *Most likely — Arduino serial monitor is still open.* Even if it's in the background, it holds the port and Python gets the connection but no data flows through. Close Arduino IDE completely and run the script again. *Second possibility — the ESP32 already sent "READY" before Python connected.* Your script skips the READY line and then waits for the next reading. With a 20ms delay on the ESP32 data should come fast, but if something is off with the timing you might be waiting. Just tilt or shake the board — if data is flowing it should trigger output. *Third — the file has a space in its name* (|week 14.py|). Notice in your terminal you had to wrap it in quotes |python "week 14.py"| — that worked, but worth renaming it to |week14.py| to avoid that friction every time. Try closing Arduino IDE completely first, that fixes it 90% of the time. What happens after you do that? 1774073276945_image.png I have the delay set to 1000ms so its readable for now 21 Mar Perfect — that's exactly what you want to see. Python is reading and parsing the ESP32 data correctly, all 6 axes printing cleanly. The serial pipeline is fully confirmed. Take a screenshot of this for the doc — this is your evidence that Layer 1 → Layer 2 is working before adding the web server complexity. Now you can move to the actual |backend.py|. The next step is running uvicorn and checking |/status| in the browser. Before you do that — change the delay back to |20| in the Arduino firmware so the data flows at 50Hz, the 1000ms was just for readability during testing. Then: 1. Save the |backend.py| file we wrote earlier 2. Run |uvicorn backend:app --port 8000| 3. Open |http://localhost:8000/status| in browser What does uvicorn output when it starts? import serial try: ser = serial.Serial('COM9', 115200, timeout=2) print(f"Connected to {ser.port} at {ser.baudrate} baud") while True: line = ser.readline().decode('utf-8').strip() if not line or line == 'READY': continue parts = line.split(',') if len(parts) != 6: continue try: ax, ay, az, gx, gy, gz = [float(x) for x in parts] print(f"accel: x={ax:.2f} y={ay:.2f} z={az:.2f} | gyro: x={gx:.2f} y={gy:.2f} z={gz:.2f}") except ValueError: continue except KeyboardInterrupt: print("\nConnection closed by user") except serial.SerialException as e: print(f"Serial error: {e}") except Exception as e: print(f"Error: {e}") finally: if 'ser' in locals() and ser.is_open: ser.close() give the code again with excpetion handling in it as well Show more 21 Mar python |import serial import serial.tools.list_ports def find_port(): for p in serial.tools.list_ports.comports(): if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340"]): return p.device return None port = find_port() if not port: print("No ESP32 found — check USB connection") exit() try: ser = serial.Serial(port, 115200, timeout=2) print(f"Connected to {ser.port} at {ser.baudrate} baud") while True: try: line = ser.readline().decode('utf-8').strip() if not line or line == 'READY': continue parts = line.split(',') if len(parts) != 6: print(f"Unexpected format: {line}") continue ax, ay, az, gx, gy, gz = [float(x) for x in parts] print(f"accel: x={ax:.2f} y={ay:.2f} z={az:.2f} | gyro: x={gx:.2f} y={gy:.2f} z={gz:.2f}") except ValueError as e: print(f"Parse error — bad data: {line} ({e})") continue except UnicodeDecodeError: print("Decode error — skipping garbled line") continue except KeyboardInterrupt: print("\nStopped by user") except serial.SerialException as e: print(f"Serial error: {e}") except Exception as e: print(f"Unexpected error: {e}") finally: if 'ser' in locals() and ser.is_open: ser.close() print("Port closed")| Two improvements over your previous version — |find_port()| auto-detects the COM port so you don't have to hardcode |COM9|, and there's now a |UnicodeDecodeError| catch for garbled bytes which is a common issue when the ESP32 first boots and sends partial lines. yeah thiis is fine but now what iis the next step in backend 21 Mar Next step is installing Mosquitto and getting the MQTT broker running, then writing the full |backend.py|. But before that — do you want to test MQTT separately first like we did with serial, or just jump straight into the full backend now that serial is confirmed working? My suggestion would be to just go straight to the full |backend.py| now since you've already confirmed the hardest part (serial reading and parsing). The flow would be: 1. Install Mosquitto and start the broker 2. Run the full |backend.py| with uvicorn 3. Check |/status| in browser — confirms serial + parsing working 4. Open a second terminal and run |mosquitto_sub -h localhost -t "fab/#" -v| — confirms MQTT working 5. Then move to the dashboard Want to start with installing Mosquitto or do you want to go straight to writing the backend first? like the data is being read so what is supposed to be next and again we are going with smaller tasks so its easier 21 Mar Next small task is just getting *FastAPI running and serving the |/status| endpoint* — nothing else yet. No MQTT, no WebSocket, just confirm the web server starts and returns the sensor data as JSON in a browser. Create |backend.py| with just this for now: python |import serial import serial.tools.list_ports import threading import time from fastapi import FastAPI app = FastAPI() latest = {} lock = threading.Lock() def find_port(): for p in serial.tools.list_ports.comports(): if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340"]): return p.device return None def serial_reader(): ser = serial.Serial(find_port(), 115200, timeout=1) time.sleep(2) while True: try: parts = ser.readline().decode('utf-8').strip().split(',') if len(parts) != 6: continue data = {k: float(v) for k, v in zip(["ax","ay","az","gx","gy","gz"], parts)} with lock: latest.update(data) except: pass @app.get("/status") def status(): with lock: return {"connected": bool(latest), "latest": latest} @app.on_event("startup") def startup(): threading.Thread(target=serial_reader, daemon=True).start()| Run it: bash |uvicorn backend:app --port 8000| Then open |http://localhost:8000/status| in your browser. What do you see? 1774073497063_image.png 21 Mar That's just a deprecation warning, not an error — the server isn't actually starting because you ran it with |python backend.py| instead of uvicorn. Run it with: bash |uvicorn backend:app --port 8000| Make sure you're running that exact command, not |python backend.py|. What does the terminal show after that? 1774073528501_image.png 21 Mar The error is |Could not import module "backend"| — uvicorn is looking for a file called |backend.py| but your file is named |week 14.py|. Either rename the file to |backend.py|, or run it using your actual filename like this: bash |uvicorn "week 14:app" --port 8000| Renaming to |backend.py| is cleaner though — just right click the file in VS Code explorer → Rename → |backend.py|. Then run: bash |uvicorn backend:app --port 8000| (venv) PS E:\shahmeer\fab_academy> uvicorn backend:app --port 8000 ERROR: Error loading ASGI app. Could not import module "backend". (venv) PS E:\shahmeer\fab_academy> uvicorn "week 14:app" --port 8000 INFO: Started server process [23424] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: 127.0.0.1:51183 - "GET / HTTP/1.1" 404 Not Found INFO: 127.0.0.1:51187 - "GET /favicon.ico HTTP/1.1" 404 Not Found 21 Mar Server is running! The 404s are fine — there's no route at |/|, only at |/status|. Open this in your browser: |http://127.0.0.1:8000/status| What do you see? 1774073593025_image.png 21 Mar You're still on |127.0.0.1:8000/| — add |/status| to the URL: |http://127.0.0.1:8000/status| 1774073612049_image.png 21 Mar That's the milestone — |connected: true| and all 6 live sensor values showing in the browser. The full serial → Python → HTTP pipeline is working. Screenshot this for the doc. Next small task is adding the *WebSocket* to the backend so the browser can receive data automatically without refreshing. Then after that MQTT. Ready to add the WebSocket code?

Intro

This week's group assignment was to explore and compare various software tools for building user interfaces that interact with microcontrollers. On the individual side, the goal was to build a working interface that talks pasted check this code and see if so far its fine. I think some small details need to be added before doing the API thing or is it fine so far in terms of documentation status 21 Mar The documentation is good so far — the progression is clear and honest, showing the actual steps you took including the errors. A few small things to fix or add: *Fix:* * The first serial reading code you show (no exception handling) — you say "it gave an error" but you should add a screenshot of that error or at least one line describing what the error was. Right now it just says it errored with no evidence. * Remove the |

| tags inside the |arduino-code| divs — you're using both and it's causing the indentation to look
    weird in the rendered page. Pick one — the |arduino-code| div is enough, drop the |
| inside it.

*Add before moving to the API section:*

  * One sentence after the serial reading screenshot saying what you
    actually saw — something like "The data was printing correctly with
    all 6 axes updating as I moved the sensor."
  * A screenshot of the parsed output (the labelled accel/gyro terminal
    output you showed me earlier — that's good evidence)
  * Then one line transitioning to the next step: "With serial confirmed
    working, the next step was building the FastAPI server to expose
    this data over HTTP and WebSocket."

*Content is fine otherwise* — the venv setup, the port detection, the
two iterations of the serial code (without then with exception handling)
is actually great documentation because it shows real process rather
than just the final answer.

Once you add those small bits you're ready to document the FastAPI |/status| step which you've already got working and screenshotted.

1774073789349_image.png

if i remove the pre tags it looks like thiis then

21 Mar

Yeah that's the |
| tag issue in reverse — without it the code loses all its line breaks
and runs together. You need to keep the |
| but remove the indentation before the code. Change this:

html

|
        import serial
...
    
| To this — |
| starts immediately, no indent before the code:

html

|
import serial

try:
    ser = serial.Serial('COM9', 115200, timeout=2)
...
| The extra spaces before the code content are what's causing the weird indentation in the rendered version. The |
| tag preserves every whitespace character exactly as written, so if
your code is indented 8 spaces in the HTML file, it renders with 8
spaces of indent in the browser.

import serial
import serial.tools.list_ports
import threading
import time
from fastapi import FastAPI
app = FastAPI()
latest = {}
lock = threading.Lock()
def find_port():
    for p in serial.tools.list_ports.comports():
        if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340"]):
            return p.device
    return None
def serial_reader():
    ser = serial.Serial(find_port(), 115200, timeout=1)
    time.sleep(2)
    while True:
        try:
            parts = ser.readline().decode('utf-8').strip().split(',')
            if len(parts) != 6:
                continue
            data = {k: float(v) for k, v in zip(["ax","ay","az","gx","gy","gz"], parts)}
            with lock:
                latest.update(data)
        except:
            pass
@app.get("/status")
def status():
    with lock:
        return {"connected": bool(latest), "latest": latest}
@app.on_event("startup")
def startup():
    threading.Thread(target=serial_reader, daemon=True).start()

explain this code the whole api part how it works, what its doing and everything and why we run that specific command

Show more
21 Mar

Here's a breakdown you can put in your doc before the code block:

html

|

With serial reading confirmed, the next step was wrapping it in a proper web server so the data can be accessed from a browser. For this I used FastAPI — a Python web framework that lets you define HTTP routes and WebSocket endpoints with minimal code. To run it, FastAPI needs a server called uvicorn — this is the actual process that listens for incoming connections on port 8000. The command uvicorn backend:app --port 8000 tells uvicorn to look inside the file called backend and find the FastAPI instance named app and serve it. You can't just run it with python backend.py because FastAPI is an async web framework — it needs uvicorn to handle the networking layer.

The code works in three parts. First, find_port() scans all connected COM ports and returns the one that looks like a USB-Serial device — this means the port doesn't need to be hardcoded, it gets detected automatically whenever the ESP32 is plugged in.

Second, serial_reader() runs in a background thread started at startup. This is important — serial reads are blocking, meaning the code just sits and waits for the next line to arrive. If this ran on the main thread it would freeze the entire web server. By putting it in a separate thread, the serial reading and the web server run independently at the same time. Every line that arrives gets split by comma into 6 values, converted to floats, and stored in the shared latest dict. The lock is used whenever latest is read or written — without it, the serial thread and the web server could try to access it at the same moment and corrupt the data.

Third, the /status route is a simple HTTP GET endpoint. When you open localhost:8000/status in a browser, FastAPI calls the status() function and returns the current contents of latest as JSON. This was the first test to confirm the full pipeline was working — serial data flowing into Python and being served over HTTP. Refreshing the page shows the values changing in real time as the sensor moves.

| okay but explain to me how this all works the whole api thing, this lock thing and threading. Like the whole theory think of me as a literal noob while doing this 21 Mar Okay, let's forget code for a second and use a real-world analogy. ------------------------------------------------------------------------ The restaurant analogy Imagine a restaurant. You have: * A *kitchen* (the ESP32 sending data) * A *waiter* (the serial reader) * A *front desk* (the web server / FastAPI) * A *customer* (your browser) ------------------------------------------------------------------------ Threading — why you need two people Imagine the waiter's only job is to stand at the kitchen window and wait for food to come out. He just stands there. Waiting. Could be 1 second, could be 5 seconds — he doesn't know. He just blocks everything else until food arrives. Now imagine this same waiter is also supposed to answer the phone at the front desk. He can't do both — if he's standing at the kitchen window waiting, the phone just rings forever. The customer gets nothing. *That's exactly the problem with serial reading.* |readline()| just sits and waits for the next line from the ESP32. If it's on the main thread, the entire web server freezes waiting for that line. Nobody can visit |/status|. *Threading* solves this by hiring a second person. One person stands at the kitchen window (serial thread), another stands at the front desk (web server). They work at the same time, independently. That's literally all threading is — running two things simultaneously. ------------------------------------------------------------------------ The lock — the shared notepad problem Both workers need to share information. So they have a notepad (|latest|) in the middle where the waiter writes down what came out of the kitchen, and the front desk reads it to tell customers. But here's the problem — what if the waiter is halfway through writing |"ax: 9.5, ay: -0"| and the front desk reads it at that exact moment? The customer gets half-written garbage data. The *lock* is a rule they agree on: /"before touching the notepad, grab the pen. When you're done, put the pen down."/ Only one person can hold the pen at a time. If the waiter has the pen and is writing, the front desk waits. Once the waiter puts it down, the front desk picks it up and reads safely. In code: python |with lock: latest.update(data) # waiter writing — pen is held| python |with lock: return latest # front desk reading — pen is held ``` The `with lock:` block automatically grabs and releases the lock. Nothing else can touch `latest` while it's held. --- ## FastAPI and uvicorn — what's the difference **FastAPI** is like the menu and the rules of the restaurant — it defines what happens when a customer asks for something. `@app.get("/status")` means "if someone requests `/status`, run this function and return the result." **uvicorn** is the actual building — the physical restaurant that listens for customers walking in. FastAPI on its own is just a Python object. It doesn't listen for connections, it doesn't handle networking. Uvicorn does all of that. It's the reason you run `uvicorn backend:app` instead of `python backend.py` — you're opening the restaurant, not just reading the menu. `--port 8000` is just the door number. The browser knows to knock on port 8000. --- ## The whole flow in one picture ``` ESP32 sends CSV line every 20ms ↓ serial thread (running in background) reads it ↓ grabs lock → writes to "latest" → releases lock ↓ meanwhile... browser visits localhost:8000/status ↓ FastAPI calls status() function ↓ grabs lock → reads "latest" → releases lock ↓ returns JSON to browser| The two arrows in the middle are happening at the same time on different threads. The lock makes sure they don't collide on |latest|. ------------------------------------------------------------------------ Does that make sense before we move to WebSockets? this is still the backend part right 21 Mar Yes still backend. The full backend journey so far is: 1. ✅ Virtual environment setup 2. ✅ Port detection test 3. ✅ Serial reading test (without exception handling → error → with exception handling → working) 4. ✅ FastAPI + |/status| endpoint — confirmed working with screenshot Still to add to the backend: 5. WebSocket endpoint — so browser gets data pushed automatically without refreshing 6. MQTT publishing — so Mosquitto broker receives the data Once those two are added to the code and tested, the backend is complete and you move to the frontend dashboard. Want to add the WebSocket next? i think here instead of showing the whole code stuff which becomes messy i should just exolain the code being used and use screenshots of output 21 Mar Yes exactly — that's much cleaner for documentation. The code is already in the files section for download, the doc doesn't need to show every iteration. The flow becomes: *Explanation of what the code does → screenshot of it working* So for the backend section the structure would be: 1. Explain venv setup → screenshot of terminal with venv active 2. Explain port detection — one line on what it does → screenshot showing COM9 detected 3. Explain serial reading — one line on what it does → screenshot of the parsed accel/gyro output 4. Explain FastAPI/threading/lock (the theory we just covered, condensed) → screenshot of |/status| returning live JSON 5. Explain WebSocket — one line → screenshot of browser dashboard updating 6. Explain MQTT — one line → screenshot of mosquitto_sub terminal Each step is: what is this doing and why → proof it worked. No code blocks in the middle of the explanation, just the final |backend.py| code block once at the end with a download link. Want me to write the condensed explanation paragraphs for steps 1-4 that you can drop straight into the doc? week 14.py 38 lines py lets do it api onwards the before part can stay as the code isnt as long. I have uploaded the file so for backend just add the proper explaintions and leave the placeholders for backend dont touch other stuff. 21 Mar Here's just the backend section to replace what you have from the API part onwards: html |

With serial reading confirmed working, the next step was building the actual backend server. I used FastAPI — a Python web framework — served by uvicorn, which is the actual process that listens for connections on port 8000. You can't run FastAPI with just python backend.py because it needs uvicorn to handle the networking. The command is always uvicorn backend:app --port 8000 — this tells uvicorn to find the FastAPI instance named app inside the file backend.py.

The backend does three things at once. A background thread runs the serial reader continuously — it has to be a separate thread because serial reads are blocking, meaning the code just sits and waits for the next line. If this ran on the main thread the entire web server would freeze every time it waited for data. A lock protects the shared latest dictionary — whenever the serial thread writes to it or the web server reads from it, the lock is held so they can never access it at the same moment and corrupt the data. The /status route is a simple HTTP endpoint that returns whatever is currently in latest as JSON — used to verify the pipeline was working before adding WebSocket.

To test it I opened http://localhost:8000/status in the browser while the ESP32 was connected. It returned connected: true with all 6 live sensor values. Refreshing the page showed the values changing as I moved the sensor, confirming the full serial → Python → HTTP pipeline was working.


// WebSocket

The /status endpoint works but requires the browser to refresh manually to see new data. WebSocket fixes this — it keeps a persistent connection open between the browser and the server so data gets pushed automatically the moment it arrives, with no polling or refreshing needed. I added a /ws WebSocket endpoint to the backend. Every time a new browser tab connects it gets added to a clients list. Each time the serial thread gets a new reading it broadcasts the JSON to every client in that list simultaneously.


// MQTT

MQTT is a lightweight messaging protocol designed for IoT — instead of sending data directly to one destination, you publish it to a topic on a broker (Mosquitto running locally), and any number of subscribers can receive it. I installed Mosquitto and added MQTT publishing to the backend — every sensor reading also gets published to the topic fab/imu. To verify it was working I opened a second terminal and ran mosquitto_sub -h localhost -t "fab/#" -v which printed every incoming message in real time.

|

With serial reading confirmed working, the next step was building the actual backend server. I used FastAPI — a Python web framework — served by uvicorn, which is the actual process that listens for connections on port 8000. You can't run FastAPI with just python backend.py because it needs uvicorn to handle the networking. The command is always uvicorn backend:app --port 8000 — this tells uvicorn to find the FastAPI instance named app inside the file backend.py.

The backend does three things at once. A background thread runs the serial reader continuously — it has to be a separate thread because serial reads are blocking, meaning the code just sits and waits for the next line. If this ran on the main thread the entire web server would freeze every time it waited for data. A lock protects the shared latest dictionary — whenever the serial thread writes to it or the web server reads from it, the lock is held so they can never access it at the same moment and corrupt the data. The /status route is a simple HTTP endpoint that returns whatever is currently in latest as JSON — used to verify the pipeline was working before adding WebSocket.

terminal

To test it I opened http://localhost:8000/status in the browser while the ESP32 was connected. It returned connected: true with all 6 live sensor values. Refreshing the page showed the values changing as I moved the sensor, confirming the full serial → Python → HTTP pipeline was working.

status endpoint
okay so far so good lets move on to adding the websocket plus will we have one backend file with all the full code or multiple because how to tell what code got added when while doing this Show more 21 Mar Good question on the file structure — *one single |backend.py|* that you build up incrementally. You don't need multiple files. The way to show progression in the doc is exactly what you're already doing — explain what you added and show a screenshot of it working, rather than showing the code at every stage. So the final |backend.py| will have everything in it: serial reader + FastAPI + WebSocket + MQTT. You show it once at the end as the complete file. Now for the WebSocket — add these imports and the two new pieces to your existing |backend.py|: python |import asyncio import json from fastapi import FastAPI, WebSocket, WebSocketDisconnect # add this to your shared variables at the top clients = [] # add this async broadcast function async def broadcast(msg): for ws in clients[:]: try: await ws.send_text(msg) except: clients.remove(ws) # add this to serial_reader() after latest.update(data) asyncio.run(broadcast(json.dumps(data))) # add this new route @app.websocket("/ws") async def ws_endpoint(ws: WebSocket): await ws.accept() clients.append(ws) try: while True: await ws.receive_text() except WebSocketDisconnect: clients.remove(ws)| Run uvicorn again and then open your browser devtools (F12) → Console and type: javascript |ws = new WebSocket('ws://localhost:8000/ws') ws.onmessage = (e) => console.log(e.data)| You should see JSON printing in the console automatically every 20ms without refreshing. Screenshot that — it's your WebSocket evidence. What do you see? import serial import serial.tools.list_ports import threading import time from fastapi import FastAPI app = FastAPI() latest = {} lock = threading.Lock() def find_port(): for p in serial.tools.list_ports.comports(): if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340"]): return p.device return None def serial_reader(): ser = serial.Serial(find_port(), 115200, timeout=1) time.sleep(2) while True: try: parts = ser.readline().decode('utf-8').strip().split(',') if len(parts) != 6: continue data = {k: float(v) for k, v in zip(["ax","ay","az","gx","gy","gz"], parts)} with lock: latest.update(data) except: pass @app.get("/status") def status(): with lock: return {"connected": bool(latest), "latest": latest} @app.on_event("startup") def startup(): threading.Thread(target=serial_reader, daemon=True).start() add them where Show more 21 Mar Here's the complete updated file with everything added in the right places — just replace your entire |backend.py| with this: python |import serial import serial.tools.list_ports import threading import asyncio import json import time from fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI() latest = {} lock = threading.Lock() clients = [] def find_port(): for p in serial.tools.list_ports.comports(): if any(x in p.description.upper() for x in ["USB", "UART", "CP210", "CH340"]): return p.device return None async def broadcast(msg): for ws in clients[:]: try: await ws.send_text(msg) except: clients.remove(ws) def serial_reader(): ser = serial.Serial(find_port(), 115200, timeout=1) time.sleep(2) while True: try: parts = ser.readline().decode('utf-8').strip().split(',') if len(parts) != 6: continue data = {k: float(v) for k, v in zip(["ax","ay","az","gx","gy","gz"], parts)} with lock: latest.update(data) asyncio.run(broadcast(json.dumps(data))) except: pass @app.get("/status") def status(): with lock: return {"connected": bool(latest), "latest": latest} @app.websocket("/ws") async def ws_endpoint(ws: WebSocket): await ws.accept() clients.append(ws) try: while True: await ws.receive_text() except WebSocketDisconnect: clients.remove(ws) @app.on_event("startup") def startup(): threading.Thread(target=serial_reader, daemon=True).start()| Run it with |uvicorn backend:app --port 8000| then test in browser console with: javascript |ws = new WebSocket('ws://localhost:8000/ws') ws.onmessage = (e) => console.log(e.data)| What do you see? wait where do i use the javascript 21 Mar In the browser — open any tab, press *F12* to open devtools, click the *Console* tab, then paste the two lines directly in there and press Enter: javascript |ws = new WebSocket('ws://localhost:8000/ws') ws.onmessage = (e) => console.log(e.data) ``` This is just a quick test to confirm the WebSocket is working before building the actual dashboard. You should see JSON strings printing automatically in the console every second without you doing anything. Like this: ``` {"ax": 9.15, "ay": -0.67, "az": -3.31, "gx": 0.05, "gy": -0.07, "gz": -0.03} {"ax": 9.12, "ay": -0.65, "az": -3.29, "gx": 0.04, "gy": -0.06, "gz": -0.02}| Make sure uvicorn is running in the VS Code terminal at the same time. What happens when you paste it? 1774075886560_image.png 21 Mar That's a Content Security Policy block — the page you have open is blocking WebSocket connections for security reasons. It's not a problem with your code, just the wrong tab to test from. Open a *blank new tab*, go to: |about:blank| Then open F12 console there and paste the JavaScript. A blank tab has no security policy so it won't block the connection. 1774075953965_image.png 21 Mar WebSocket is working perfectly. JSON packets streaming in automatically, all 6 axes updating in real time. The yellow warning is just a Firefox devtools internal thing, ignore it completely — it's not related to your code. Screenshot this for the doc — this is your WebSocket evidence. Next step is adding MQTT. Do you have Mosquitto installed already or do we need to do that first? but the webpage itself doesnt seem to update the values until you press refresh 21 Mar That's expected — the |/status| endpoint is just a regular HTTP request, it only returns data when you ask for it (when you refresh). That's exactly why we added WebSocket — the dashboard HTML page will use WebSocket to update automatically without refreshing. The console test you just did proves WebSocket is pushing data continuously. Now we just need to build the actual |dashboard.html| that connects to the WebSocket and updates the numbers on screen in real time — that's the frontend step. So the order is: 1. ✅ WebSocket confirmed working in console 2. Skip MQTT for now — do the dashboard first so you can actually see it working visually 3. Add MQTT after Want to build the dashboard now? yes 21 Mar Here's the complete |dashboard.html| — save it in the same folder as your |backend.py| and open it in the browser: html | IMU Dashboard

IMU live dashboard — LSM6DS33

accel X
accel Y
accel Z
gyro X
gyro Y
gyro Z

Accelerometer (m/s²)

Gyroscope (rad/s)

| Make sure uvicorn is still running, then just double click |dashboard.html| to open it in the browser. You should see the green dot appear and the numbers start updating automatically. What do you see? 1774076148099_image.png 21 Mar The dashboard is loading but the dot is grey and values show |—| — WebSocket isn't connecting. The issue is you opened it as a local file (|file:///E:/shahmeer/...|) but WebSocket needs it served over HTTP. Two options to fix this: *Option 1 — serve dashboard from FastAPI (easiest).* Add this route to your |backend.py|: python |from fastapi.responses import FileResponse @app.get("/") def dashboard(): return FileResponse("dashboard.html")| Then instead of opening the file directly, go to |http://localhost:8000| in the browser. The dashboard will load from the server and the WebSocket connection will work. *Option 2 — use VS Code Live Server extension.* Install "Live Server" in VS Code extensions, right click |dashboard.html| → Open with Live Server. It serves the file over HTTP on port 5500. Option 1 is cleaner — try adding that route and visiting |localhost:8000|. What happens? 1774076278369_image.png 21 Mar That's the whole thing working. Green dot, live numbers updating, both charts scrolling in real time, gyro spiking when you move it. That's your full stack — ESP32 → serial → Python → WebSocket → browser. Screenshot this and record a short video of you moving the sensor and the charts reacting — that's your main evidence for the week. After that you just need MQTT and then the doc is essentially done. Do you want to add MQTT next or document what you have first? lets stop here for now. First give me the whole documentation explaintion for frontend to add. Make it sound human and like someone who is doing it for the first time 21 Mar html |

// browser dashboard

With the backend sending data over WebSocket, the next step was building the actual page that displays it. I created a plain HTML file called dashboard.html — no frameworks, no build tools, just HTML, CSS and vanilla JavaScript. The idea is simple: connect to the WebSocket, and every time a message arrives update the numbers and push the new values onto the charts.

For the charts I used Chart.js, a JavaScript library that handles all the drawing. I made two line charts — one for the accelerometer (X, Y, Z) and one for the gyroscope (X, Y, Z). Each chart keeps the last 80 data points and drops the oldest one as new data comes in, so it scrolls continuously rather than growing forever. Above the charts are six number readouts that show the current value of each axis updating in real time.

The first time I opened the file by just double clicking it, the dashboard loaded but nothing worked — the dot stayed grey and all values showed . The issue was that opening it as a local file (file:///) blocks WebSocket connections for security reasons. The fix was to serve the dashboard through FastAPI by adding a route that returns the HTML file, then opening it at http://localhost:8000 instead. Once I did that the green dot appeared immediately and the charts started updating on their own.

Moving and shaking the sensor causes the accelerometer values to spike sharply and the gyroscope values to jump whenever there is rotation. Leaving it flat and still brings everything back to a stable baseline. Seeing the data react in real time to physical movement in the browser was a satisfying moment — it confirmed the entire chain from sensor to screen was working.

| edit the part where i diid it. just say you "claude" did the webpage making and styling 21 Mar html |

// browser dashboard

With the backend sending data over WebSocket, the next step was building the actual page that displays it. The dashboard HTML was made with the help of Claude — I gave it the WebSocket endpoint and what data was coming through and it generated the page with the charts and live readouts. It is a plain HTML file with no frameworks or build tools, just HTML, CSS and vanilla JavaScript.

The page uses Chart.js for the charts — two line charts, one for the accelerometer (X, Y, Z) and one for the gyroscope (X, Y, Z). Each chart keeps the last 80 data points and drops the oldest as new ones come in so it scrolls continuously. Above the charts are six number readouts showing the current value of each axis.

The first time I opened the file by just double clicking it, the dashboard loaded but nothing worked — the dot stayed grey and all values showed . The issue was that opening it as a local file (file:///) blocks WebSocket connections for security reasons. The fix was to add a route in FastAPI that serves the HTML file, then opening it at http://localhost:8000 instead. Once I did that the green dot appeared and the charts started updating on their own.

Moving and shaking the sensor causes the accelerometer values to spike and the gyroscope values to jump when there is rotation. Leaving it flat brings everything back to a stable baseline.

| in the backend web socket what screenshot should i add and where will i have the javascipt commands i used 21 Mar 2 / 2 For the WebSocket section in the backend, add two screenshots: *Screenshot 1* — the browser console showing the JSON streaming in (the one you already took with the |about:blank| tab). This goes right after the paragraph explaining what WebSocket does, before you talk about the dashboard. It proves WebSocket was working before the dashboard was even built. *Screenshot 2* — the uvicorn terminal showing a connection was accepted when the browser connected. You can get this by adding a print statement to the ws endpoint, or just showing the terminal running. For the JavaScript commands — add a small callout like this right before screenshot 1: html |

To test the WebSocket before building the dashboard, I opened a blank browser tab, pressed F12 to open the developer console and ran the following JavaScript directly in it:

ws = new WebSocket('ws://localhost:8000/ws')
ws.onmessage = (e) => console.log(e.data)

This creates a WebSocket connection and prints every incoming message to the console. JSON packets started appearing immediately and automatically without any refreshing, confirming the server was pushing data correctly.

| That way the JavaScript is documented in context — it was a test step, not part of the final code, which is accurate. okay I am doing all of this the next day. so lets go back to the websocket part rght after we did the http page where we had to refresh to get the new set of values 23 Mar Right so where we left off — you had |/status| working, confirmed with the browser showing live JSON. The issue was you had to refresh to see new values. That's the exact limitation WebSocket solves. The updated |backend.py| with WebSocket added is the one I gave you earlier with these additions: * |clients = []| at the top * |broadcast()| async function * |asyncio.run(broadcast(...))| inside the serial reader after updating |latest| * the |/ws| WebSocket route at the bottom You already tested this worked using the browser console on the |about:blank| tab and saw JSON streaming automatically. When you come back tomorrow the next steps are: 1. Add the |FileResponse| route to serve |dashboard.html| from FastAPI 2. Open |localhost:8000| and confirm green dot + live charts 3. Record a short video of moving the sensor 4. Then add MQTT as the final backend piece 5. Then document everything Is there anything you want to clarify or prep before you pick it up tomorrow? no the part where we typed javascript into the console to see the values 23 Mar Right — here's the sequence for that part of the doc: You had |/status| working but had to refresh manually. So the next step was testing if WebSocket was actually pushing data without refreshing. To test it: 1. Updated |backend.py| with the WebSocket code 2. Restarted uvicorn 3. Opened a blank tab (|about:blank|) 4. Pressed F12 → Console 5. Typed the two JavaScript lines 6. JSON started streaming automatically First attempt was on the wrong tab (your Fab Academy page) and got the Content Security Policy error. Then switched to |about:blank| and it worked. That whole sequence — the CSP error screenshot, then the working console screenshot — is good honest documentation showing what actually happened. Both screenshots you already have. Does that match what you remember or did something happen differently that we should document accurately?

To test it I opened http://localhost:8000/status in the browser while the ESP32C3 was connected. It returned connected: true with all 6 live sensor values. Refreshing the page showed the values changing as I moved the sensor, confirming the full serial → Python → HTTP pipeline was working.

status endpoint

// WebSocket

So once that was done I added the websocket

The /status endpoint works but requires the browser to refresh manually to see new data. WebSocket fixes this — it keeps a persistent connection open between the browser and the server so data gets pushed automatically the moment it arrives, with no polling or refreshing needed. I added a /ws WebSocket endpoint to the backend. Every time a new browser tab connects it gets added to a clients list. Each time the serial thread gets a new reading it broadcasts the JSON to every client in that list simultaneously.

what do i write above about the websocket part where we did the javascript and console stuff Show more 23 Mar Replace your current WebSocket section with this: html |

// WebSocket

The /status endpoint works but requires the browser to refresh manually to see new data. WebSocket fixes this — it keeps a persistent connection open between the browser and the server so data gets pushed automatically the moment it arrives, with no polling or refreshing needed. I added a /ws WebSocket endpoint to the backend. Every time a new browser tab connects it gets added to a clients list. Each time the serial thread gets a new reading it broadcasts the JSON to every client in that list simultaneously.

Before building the dashboard I wanted to confirm the WebSocket was actually working. I opened a blank tab in the browser, pressed F12 to open the developer console and typed in these two lines of JavaScript:

ws = new WebSocket('ws://localhost:8000/ws')
ws.onmessage = (e) => console.log(e.data)

The first attempt was on my Fab Academy page and it gave a Content Security Policy error blocking the connection. Switching to an empty about:blank tab fixed it. JSON packets started printing in the console automatically without any refreshing, confirming the WebSocket was pushing data correctly.

| give a short explaiantion of what a websocket is to add into the documentation 23 Mar html |

A WebSocket is a communication protocol that keeps a permanent two-way connection open between the browser and the server. Unlike a normal HTTP request where the browser asks for data and the server responds then closes the connection, a WebSocket stays open the entire time — the server can push data to the browser whenever it wants without the browser having to ask. This makes it ideal for live data like sensor readings where you want the page to update the moment new data arrives rather than having to refresh.

| explain the opening directly part to me why the values dont update and stuff and why we added the html fle to the backend 23 Mar When you open an HTML file by double clicking it, the browser loads it using the |file:///| protocol — it's reading it straight from your hard drive like opening a text file, not over a network. Browsers treat this as untrusted and block certain things for security, and WebSocket connections to external servers is one of them. So the page loads fine, the charts appear, but the WebSocket connection silently fails — nothing connects to |localhost:8000/ws| and no data ever arrives. That's why everything showed |—| and the dot stayed grey. When you add the HTML file as a route in FastAPI and visit |localhost:8000| instead, the browser now loads the page over HTTP — the same protocol it uses for normal websites. It trusts this, WebSocket connections are allowed, and |localhost:8000/ws| connects immediately. The short version — |file:///| is the browser reading a file from disk, |http://| is the browser talking to a server. WebSocket only works in the second case. Week 14: Interface and Application programming Week 9: Input Devices Intro

This week's group assignment was to explore and compare various software tools for building user interfaces that interact with microcontrollers. On the individual side, the goal was to build a working interface that talks t pasted

Python backend

I am using visual studio code for this. So first I set up the virtual environment. As I was setting it up, it gave me an error saying running scripts was disabled. So I went to powershell and ran the commands shown in the picture afte pasted

Intro

This week's group assignment was to explore and compare various software tools for building user interfaces that interact with microcontrollers. On the individual side, the goal was to build a working interface that talks pasted week 14.py 38 lines py Week 14: Interface and Application programming